package com.zzyl.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.zzyl.base.PageResponse;
import com.zzyl.constant.CacheConstants;
import com.zzyl.dto.AlertRuleDto;
import com.zzyl.entity.AlertData;
import com.zzyl.entity.AlertRule;
import com.zzyl.mapper.AlertDataMapper;
import com.zzyl.mapper.AlertRuleMapper;
import com.zzyl.mapper.DeviceMapper;
import com.zzyl.mapper.UserRoleMapper;
import com.zzyl.service.AlertRuleService;
import com.zzyl.utils.StringUtils;
import com.zzyl.vo.AlertRuleVo;
import com.zzyl.vo.DeviceDataVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
public class AlertRuleServiceImpl implements AlertRuleService {
    @Resource
    private AlertRuleMapper alertRuleMapper;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private DeviceMapper deviceMapper;

    /**
     * 新增告警规则
     *
     * @param alertRuleDto
     */
    @Override
    public void createAlertRule(AlertRuleDto alertRuleDto) {
        AlertRule bean = BeanUtil.toBean(alertRuleDto, AlertRule.class);
        alertRuleMapper.insertAlertRule(bean);
    }

    /**
     * 分页获取告警规则列表
     *
     * @param pageNum
     * @param pageSize
     * @param alertRuleName
     * @param functionName
     * @param productKey
     * @return
     */
    @Override
    public PageResponse<AlertRuleVo> getPageAlertRule(Integer pageNum, Integer pageSize, String alertRuleName, String functionName, String productKey) {
        PageHelper.startPage(pageNum, pageSize);
        Page<AlertRule> page = alertRuleMapper.getPageAlertRule(alertRuleName, functionName, productKey);

        return PageResponse.of(page, AlertRuleVo.class);
    }

    /**
     * 获取单个告警规则
     *
     * @param id
     * @return
     */
    @Override
    public AlertRuleVo readById(Long id) {
        AlertRule alertRule = alertRuleMapper.readById(id);
        AlertRuleVo alertRuleVo = BeanUtil.toBean(alertRule, AlertRuleVo.class);
        return alertRuleVo;
    }

    /**
     * 更新告警规则
     *
     * @param alertRuleDto
     */
    @Override
    public void updateById(AlertRuleDto alertRuleDto) {
        AlertRule alertRule = BeanUtil.toBean(alertRuleDto, AlertRule.class);

        alertRuleMapper.updateById(alertRule);
    }

    /**
     * 删除告警规则
     *
     * @param id
     */
    @Override
    public void deleteById(long id) {
        alertRuleMapper.deleteById(id);
    }

    /**
     * 启用禁用
     *
     * @param id
     * @param status
     */
    @Override
    public void okNo(long id, Integer status) {
        alertRuleMapper.okNo(id, status);
    }

    /**
     * 对每一条设备上报数据进行报警规则校验
     *
     * @param d
     */
    @Override
    public void alertFilter(DeviceDataVo d) {
        //获取设备上报数据时间，如果上报发生在1分钟前，为历史数据，不再处理
        LocalDateTime alarmTime = d.getAlarmTime();
        long between = LocalDateTimeUtil.between(alarmTime, LocalDateTime.now(), ChronoUnit.SECONDS);
        if (between > 60) {
            // 超过了60秒 不处理
            return;
        }
        // 查询报警规则，将对于全部设备的报警规则 和 对于本设备的报警规则合并
        // 独特个体的单个报警规则
        List<AlertRule> TheAlertRules = alertRuleMapper.selectByFunctionId(d.getFunctionId(), d.getIotId(), d.getProductKey());
        // 同类产品的共有报警规则
        List<AlertRule> AllAlertRules1 = alertRuleMapper.selectByFunctionId(d.getFunctionId(), "-1", d.getProductKey());
        // 规则放在一起集合
        Collection<AlertRule> alertRules = CollUtil.addAll(TheAlertRules, AllAlertRules1);
        // 如果报警规则集合为空，程序结束
        if (CollUtil.isEmpty(alertRules)) {
            return;
        }
        alertRules.forEach(c -> deviceDateAlarmHandler(d, c));
    }

    @Autowired
    private UserRoleMapper userRoleMapper;
    //设备维护人员的角色名称
    @Value("${zzyl.alert.deviceMaintainerRole}")
    private String deviceMaintainerRole;
    //超级管理员的角色名称
    @Value("${zzyl.alert.managerRole}")
    private String managerRole;

    /**
     * 设备数据报警处理器
     *
     * @param d 设备上报数据
     * @param c 报警规则
     */
    private void deviceDateAlarmHandler(DeviceDataVo d, AlertRule c) {
        // 第一次校验，判断是否在生效时间内
        // 数据上报的时间
        LocalTime localTime = d.getAlarmTime().toLocalTime();
        String alertEffectivePeriod = c.getAlertEffectivePeriod();
        String[] split = alertEffectivePeriod.split("~");
        // 生效开始时间
        LocalTime start = LocalTime.parse(split[0]);
        // 生效结束时间
        LocalTime end = LocalTime.parse(split[1]);

        if (start.isAfter(localTime) || end.isBefore(localTime)) {
            // 不在生效时间里 校验结束
            return;
        }
        //第二层校验：将设备上报值和阈值比较
        // 设备id
        String iotId = d.getIotId();
        // 从redis中获取数据  报警规则连续触发次数redisKey
        String aggCountKey = CacheConstants.ALERT_TRIGGER_COUNT_PREFIX +
                iotId + ":" + d.getFunctionId() + ":" + c.getId();
        String dataValue = d.getDataValue();
        Float aFloat = Convert.toFloat(dataValue);

        String operator = c.getOperator();
        Float value = c.getValue();
        if (("<".equals(operator) && aFloat < value) || (">=".equals(operator) && aFloat >= value)) {
            // 达到阈值发消息通知
        } else {
            // 清空redis中持续周期的计数
            //该情况不符合报警规则，所以设备上报数据为正常数据。需要删除redis聚合的异常数据,程序结束
            stringRedisTemplate.delete(aggCountKey);
            return;
        }
        // 第三次校验 校验当前设备+功能的报警规则是否处于沉默周期
        String silentCacheKey = CacheConstants.ALERT_SILENT_PREFIX + iotId + ":" + d.getFunctionId() + ":" + c.getId();
        String silentData = stringRedisTemplate.opsForValue().get(silentCacheKey);
        // 如果redis中存在对应沉默周期数据，则当前设备+功能正处于沉默周期，无需报警
        if (StringUtils.isNotEmpty(silentData)) {
            return;
        }

        // 第四次校验，校验持续周期
        // 连续周期次数加1 increment让本身的值自增+1，如果key不存在则设置key并值为1，执行后会返回最新的值
        Long count = stringRedisTemplate.opsForValue().increment(aggCountKey);
        // 判断是否达到持续周期
        if (count < c.getDuration()) {
            //  没有达到 持续周期的次数，不处理
            return;
        }
        // 删除redis报警数
        stringRedisTemplate.delete(aggCountKey);
        // 添加对应的沉默周期，设置过期时间添加对应的沉默周期，设置过期时间
        stringRedisTemplate.opsForValue().set(silentCacheKey, d.getDataValue(), c.getAlertSilentPeriod(), TimeUnit.MINUTES);

        List<Long> userId = new ArrayList<>();
        if ("0".equals(c.getAlertDataType())) {
            // 如果是老人报警数据，需要通知绑定老人的护理员。根据iotId查询老人绑定的护理员
            if (d.getLocationType() == 0) {
                // 绑定老人的设备报警数据
                userId = deviceMapper.selectNursingIdsByIotIdWithElder(iotId);
            } else if (d.getLocationType() == 1 && d.getPhysicalLocationType() == 2) {
                userId = deviceMapper.selectNursingIdsByIotIdWithBed(iotId);
            }
        } else {
            //如果是设备报警数据，需要通知设备维护人员。根据指定角色名称查询相关用户
            userId = userRoleMapper.selectByRoleName(deviceMaintainerRole);
        }
        // 查询超级管理员 一定要查无论情况
        List<Long> managerIds = userRoleMapper.selectByRoleName(managerRole);
        List<Long> allConsumerId = CollUtil.addAllIfNotContains(userId, managerIds);
        List<Long> allConsumerIds = CollUtil.distinct(allConsumerId);

        // 新增报警数据
        insertAlertData(d, c, allConsumerIds);
    }

    @Autowired
    private AlertDataMapper alertDataMapper;

    private void insertAlertData(DeviceDataVo deviceDataVo, AlertRule alertRule, List<Long> consumerIds) {
        String alertReason = CharSequenceUtil.format("{}{}{},持续{}个周期就报警", alertRule.getFunctionName(), alertRule.getOperator(), alertRule.getValue(), alertRule.getDuration());
        AlertData alertData = BeanUtil.toBean(deviceDataVo, AlertData.class);
        alertData.setAlertRuleId(alertRule.getId());
        alertData.setAlertReason(alertReason);
        alertData.setType(alertRule.getAlertDataType());
        alertData.setStatus(0);
        List<AlertData> list = consumerIds.stream().map(id -> {
            AlertData dbAlertData = BeanUtil.toBean(alertData, AlertData.class);
            dbAlertData.setUserId(id);
            return dbAlertData;
        }).collect(Collectors.toList());
        alertDataMapper.batchInsert(list);
    }
}
